import axios from 'axios';

let uid = 100000;
let dealUrl = '',
    addName = '',
    addTime = '',
    response = {};

export function genUid() {
    const now = String(Date.now());
    const r = String(Math.random()).slice(-6);
    return now + r + String(uid++);
}

const camelizeRE = /-(\w)/g;
const camelize = (str) => {
    return str.toLowerCase().replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
};

const httpUploadQueue = [];

export default class RawaOss {
    constructor(options) {
        this.options = options;
    }

    // options!: RawaOssOptions
    options;

    checkpoint;

    // checkpoint!: RawaOssCheckpoint

    queue = []; // eslint-disable-line

    // uploadOptions!: RawaOssMultipartUploadOptions

    resolve = null;

    reject = null;

    aborted = false;

    paused = false;

    multipartUpload(name, file, options = {}, moduleType, that) {
        const { parallel, partSize, progress, checkpoint, timeout } = options;
        this.uploadOptions = options;
        this.checkpoint = checkpoint || this.initCheckPoint(name, file, partSize);
        return new Promise((resolve, reject) => {
            this.resolve = resolve;
            this.reject = reject;
            let resolved = false;
            const handler = () => {
                const chunkData = this.getChunk();
                const fileParams = {
                    bucket: this.options.bucket,
                    name: name,
                    data: null,
                    etag: '',
                    res: {
                        status: 200,
                        dealUrl,
                        headers: {},
                        size: this.checkpoint.fileSize,
                        rt: 0,
                        requestUrls: [`${this.getBaseUrl()}${this.getRelativePath()}`],
                        addName,
                        addTime
                    }
                };
                const completeFn = () => {
                    Object.assign(fileParams.res, { dealUrl, addName, addTime });
                    fileParams.response = response;
                    resolve(fileParams);
                };
                if (!chunkData && this.queue.length < 1) {
                    // 已上传完成
                    if (!resolved) {
                        completeFn();
                        resolved = true;
                    }
                    return;
                }
                if (chunkData && !this.paused && !this.aborted) {
                    const source = axios.CancelToken.source();
                    this.queue.push({ number: chunkData.number, source });
                    // this.queue.unshift({ number: chunkData.number, source });
                    // 增加fileParams传参
                    this.upload(chunkData, source, fileParams, file, moduleType)
                        .then((value) => {
                            const data = value?.data?.data;
                            dealUrl = data?.url;
                            if (data?.annex) {
                                addName = data?.annex?.addName;
                                addTime = data?.annex?.addTime;
                                response = data;
                            }

                            const idx = this.queue.findIndex((v) => v.number === chunkData.number);
                            if (idx >= 0) this.queue.splice(idx, 1);
                            this.checkpoint.doneParts.push({ number: chunkData.number, etag: '' });
                            if (progress) {
                                // 更新进度
                                const p = this.checkpoint.doneParts.length / chunkData.total;
                                progress(p, this.checkpoint);
                            }

                            const cd = this.getChunk();
                            if (!cd && this.queue.length < 1) {
                                completeFn();
                            } else {
                                handler();
                            }
                        })
                        .catch((e) => {
                            reject(e);
                        });
                }
            };

            [...Array(parallel || 5).keys()].forEach((v) => {
                handler();
            });
        });
    }

    signatureUrl(name, options) {
        const b = location.origin + '/api/oss/download';
        const f = '/download/';
        const p = name.substr(name.indexOf(f) + f.length - 1);
        const u = new URL(b + p);
        const response = options?.response || {};
        Object.entries(response).forEach(([k, v]) => {
            u.searchParams.append(camelize(k), v);
        });
        return u.href;
    }

    cancel() {
        this.paused = true;
        this.queue.forEach((v) => v.source.cancel());
        if (this.reject) this.reject();
    }

    abortMultipartUpload(name, uploadId, options) {
        this.aborted = true;
        this.queue.forEach((v) => v.source.cancel());
        if (this.reject) this.reject({ name: 'abort' });
        return Promise.resolve(null);
    }

    isCancel() {
        return this.paused;
    }

    initCheckPoint(name, file, partSize = 1024 * 1024) {
        const maxParts = 10000;
        const size = file.size;
        partSize = Math.max(Math.ceil(size / maxParts), partSize);

        return {
            file,
            name,
            fileSize: size,
            partSize,
            uploadId: genUid(),
            doneParts: []
        };
    }

    getChunk() {
        const { fileSize, partSize } = this.checkpoint;
        const set = new Set([...this.checkpoint.doneParts.map((v) => v.number), ...this.queue.map((v) => v.number)]);
        const total = Math.ceil(fileSize / partSize);
        if (set.size >= total) return null;
        let number = 0;
        for (let i = 0; i < total; i++) {
            if (!set.has(i)) {
                number = i;
                break;
            }
        }
        const start = number * partSize;
        const end = Math.min(start + partSize, fileSize);
        const blob = this.checkpoint.file.slice(start, end);
        return { number, blob, total };
    }

    getRequest(type = 'download') {
        return axios.create({
            baseURL: this.getBaseUrl(type),
            timeout: this.uploadOptions.timeout || 30 * 60 * 1000,
            withCredentials: false
        });
    }

    upload({ number, blob, total }, source, fileParams, file, moduleType) {
        const max = 5;
        const handle = () => {
            const num = httpUploadQueue.filter((v) => v.uploading).length;
            const todos = httpUploadQueue.filter((v) => !v.uploading);
            if (num >= 5) return;
            const pendings = todos.slice(0, max - num);
            pendings.forEach((v) => {
                v.uploading = true;
                v.fn().finally(() => {
                    const idx = httpUploadQueue.findIndex((w) => w.key === v.key);
                    if (idx >= 0) {
                        httpUploadQueue.splice(idx, 1);
                    }
                    handle();
                });
            });
        };
        return new Promise((resolve, reject) => {
            const action = () => {
                const request = this.getRequest('upload');
                request.defaults.headers.token = JSON.parse(sessionStorage.getItem('vuex'))?.user?.token;
                const data = new FormData();
                data.append('file', blob);
                let params = {
                    chunkNum: number,
                    chunkTotal: total,
                    uploadId: this.checkpoint.uploadId,
                    uploadType: 2,
                    module: moduleType //所属模块
                };
                // 判断total-1 === number 说明是最后一个分片上传 传参带入url
                if (total - 1 === number) {
                    const getUrl = fileParams?.res?.dealUrl;
                    params.name = file.webkitRelativePath || file.name;
                    params.size = file.size;
                    params.url = getUrl.endsWith('dwg') || getUrl.endsWith('dxf') ? getUrl : fileParams?.res.requestUrls[0];
                }
                return request
                    .post(this.getRelativePath(), data, {
                        cancelToken: source.token,
                        params
                    })
                    .then((res) => {
                        resolve(res);
                    })
                    .catch((err) => {
                        reject(err);
                    });
            };
            const item = {
                key: genUid(),
                uploading: false,
                fn: action
            };
            httpUploadQueue.push(item); //push队列数据，请求接口数据
            // httpUploadQueue.unshift(item);
            handle();
        });
    }

    getBaseUrl(type = 'download') {
        const baseUrl = axios.defaults.baseURL;
        const bucket = this.options?.bucket || 'fs';
        const path = `/document/v1/oss/${type === 'upload' ? 'annex/' : ''}`;
        return baseUrl + path + type + '/' + bucket;
    }

    getRelativePath(name) {
        return `/${name || this.checkpoint?.name}`;
    }
}
